/*
 * T.Raynal - A.Aviles - F.Diallo
 * XML-Scenarios
 * PPA AL5-2
 * December 2012 
 */
package model;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Stack;

import model.serialization.XmlSerializable;
import model.serialization.SerializationException;
import model.serialization.XmlArray;
import model.serialization.XmlAttribute;
import model.serialization.XmlElement;
import model.serialization.XmlValue;
import model.situation.Item;
import org.jdom2.Element;


// TODO: Auto-generated Javadoc
/**
 * Serializer for XmlElement
 *
 * @param <T> Type of serializable XmlElement
 */
public class Serializer<T extends XmlSerializable> {
	
	/**
	 * Handle element.
	 *
	 * @param XmlElement
	 * @return DOM Element
	 * @throws SerializationException
	 */
	public Element handleElement (T xml_element) throws SerializationException
	{
		
		if (null!=(xml_element.getClass().getAnnotation(XmlElement.class) ))
		{
			String v1=null,v2 =null;
			Element element = null;

			if (null==(v1 = xml_element.getClass().getAnnotation(XmlElement.class).name()))
				throw new SerializationException("XMLElement name must be specified");
			
			element = new Element(v1);
			
			for(Field field : xml_element.getClass().getFields()){
				
				Annotation[] annotations = field.getAnnotations();
				
					for (int i =0; i<annotations.length;i++)
					{
						if (annotations[i] instanceof XmlAttribute)
						{
							
							try {
								if (null==(v1=((XmlAttribute)annotations[i]).name())
										||
										null==(v2=(field.get(xml_element).toString())))
								{
									throw new SerializationException("Missing name or value on attribute");
								}
							} catch (IllegalArgumentException
									| IllegalAccessException e) {
								throw new SerializationException("Missing name or value on attribute");
							}

							element.setAttribute(v1, v2);

						}
						else if (annotations[i] instanceof XmlArray)
						{
							List<Element> l = handleStructure(annotations[i],field,xml_element);
							
							for (Element e : l) {
								element.addContent(e);
							}
						}
						
						else if (annotations[i] instanceof XmlValue)
						{
							throw new SerializationException("Unimplemented");
						}
					}
				}
			
			return element;
			
		}
		else
		{
			throw new SerializationException(xml_element.getClass().getName() + "is not an XMLElement");
		}
		

	}
	
	/**
	 * Handle a structure of XmlElement.
	 *
	 * @param Type of structure
	 * @param Structure
	 * @param Type of XmlElement
	 * @return List of DOM element
	 * @throws SerializationException
	 */
	private List<Element> handleStructure(Annotation annotation, Field field, T object) throws SerializationException//IllegalArgumentException, IllegalAccessException
	{
		Object struct= null;
		
		XmlArray.STRUCTURE structure;
		XmlArray.ELEMENT type_element;
		
		Element root = null;
		
		int nbOperand =0;
		
		List<Element> elements = new ArrayList<>();
	
		boolean hasContainer = false;
		String name =null;
		
		try {
			if (null == (struct=field.get(object)))
			{
				throw new SerializationException("XMLArray not initialized");
			}
		} catch (IllegalArgumentException | IllegalAccessException e) {
			throw new SerializationException(e.getMessage());
		}
		

		if(null==(structure= ((XmlArray)annotation).structure())
				||
		  (null==(name=((XmlArray)annotation).name())
		  		||
		  (null==(type_element=((XmlArray)annotation).type()))
				  ))
		  {
			throw new SerializationException("Missing identifiers in XMLArray element " + field.getName());
		  }

         hasContainer =((XmlArray)annotation).hasContainer();
        
         if (hasContainer)
        	 elements.add(new Element(name));
 		
		
		if (structure == XmlArray.STRUCTURE.ARRAY_LIST)
		{
			switch (type_element)
			{
				case EXPRESSION:
					throw new SerializationException("Unimplemented");
				case SCENARIO:
					throw new SerializationException("Unimplemented");
				case SITUATION:
					throw new SerializationException("Unimplemented");
				case CASE:
					throw new SerializationException("Unimplemented");
			}
		}
		else if (structure == XmlArray.STRUCTURE.HASH_MAP)
		{
		
			switch (type_element)
			{
				case EXPRESSION:
					throw new SerializationException("Unimplemented");
				case SCENARIO:
					throw new SerializationException("Unimplemented");
				case CASE:
					throw new SerializationException("Unimplemented");					
				case SITUATION:
					@SuppressWarnings("unchecked")
					HashMap<String,Situation> hash = (HashMap<String,Situation>)struct;
					Serializer<Situation> serializer = new Serializer<>();
					for (Situation situation: hash.values()) {
						
						Element e = serializer.handleElement(situation);
						
						  if (hasContainer)
						  {
							  elements.get(0).addContent(e);
						  }
						  
						  else
						  {
							  elements.add(e);
						  }
					}
					
					break;
			}
			
		}
		
		else if (structure == XmlArray.STRUCTURE.QUEUE)
		{
			switch (type_element)
			{
				case EXPRESSION:
					throw new SerializationException("Unimplemented");
				case SCENARIO:
					throw new SerializationException("Unimplemented");
				case CASE:
					@SuppressWarnings("unchecked")
					Queue<Case> hash =  (Queue<Case>)struct;
					Serializer<Case> serializer = new Serializer<>();	
					Iterator<Case> iterator  = hash.iterator();
					Case c_case = null;
					
				while (iterator.hasNext()) 
				{
					c_case = iterator.next();
					
						Element e = serializer.handleElement(c_case);
						
						  if (hasContainer)
						  {
							  elements.get(0).addContent(e);
						  }
						  
						  else
						  {
							  elements.add(e);
						  }
					}
					
				break;
				case SITUATION:
					throw new SerializationException("Unimplemented");
			}
		}
		
		else if (structure == XmlArray.STRUCTURE.STACK)
		{
			switch (type_element)
			{
			case CASE:
				throw new SerializationException("Unimplemented");
			case EXPRESSION:
				@SuppressWarnings("unchecked")
				Stack<Item> hash =  (Stack<Item>)struct;
				
				for (Item expression: hash) {

					switch(expression.getType())
					{
						case EXPRESSION:
							Serializer<Expression> serializer = new Serializer<>();
							Expression exp = (Expression)expression.releaseItem();
							Element e = serializer.handleElement(exp);
							
							  if (hasContainer)
							  {
								  if (null==root)
								  {
									  elements.get(0).addContent(e);
								  }
								  else
								  {
									  root.addContent(e);
								  }
							  }
							  else
							  {
								  if (null==root)
								  {
									  root = e;
									  elements.add(root);
								  }
								  else
								  {
									  root.addContent(e);
								  }
							  }
							  
							  nbOperand++;
							  
							  if (nbOperand==2)
							  {
								  root =root.getParentElement().getParentElement();
								  nbOperand=0;
							  }
							  
							  else
							  {
								  root =root.getParentElement();
							  }
							  
							  
							  
							break;
							
						case OPERATOR:
							  if (hasContainer)
							  {
								  if (null==root)
								  {
									  root = new Element("OPERATOR");
									  root.setAttribute("name",expression.releaseItem().toString());
									  elements.get(0).addContent(root);
								  }
								  else
								  {
									  Element op = new Element("OPERATOR");
									  op.setAttribute("name",expression.releaseItem().toString());
									  root.addContent(op);
									  root = op;
								  }
							  }
							  else
							  {
								  
								  System.out.println(expression.releaseItem().toString()); 
								  if (null==root)
								  {
									  root = new Element("OPERATOR");
									  root.setAttribute("name",expression.releaseItem().toString());
									  elements.add(root);
								  }
								  else
								  {
									  Element op = new Element("OPERATOR");
									  op.setAttribute("name",expression.releaseItem().toString());
									  root.addContent(op);
									  root = op;						
								  }
							  }
							break;
					}
				}
				break;
			case SCENARIO:
				throw new SerializationException("Unimplemented");
			case SITUATION:
				throw new SerializationException("Unimplemented");
			}
		}
		
		return elements;
	}
	
}
